#!/usr/bin/python2
# -*- coding: utf-8 -*-

import time

class Operation:
    ADD = 0
    MOD = 1
    DEL = 2

    def __init__(self,type,dpid,priority,match,action):
        """
        Operation encapsulation
        :param type: ADD,MOD,DEL
        :param dpid: dpid
        :param priority:
        :param match: {"src":1}
        :param action: {"output":[]}
        """
        self.type = type
        self.dpid = dpid
        self.priority = priority
        self.match = match
        self.action = action


class DependencyNode:
    INIT = 0
    EXECED = 1
    WAIT = 2
    def __init__(self,op):
        self.op = op
        self.acquire = []
        self.release = []
        self.status = DependencyNode.INIT


class DependencyGraph:
    def __init__(self):
        self.nodes = None

    @classmethod
    def merge_different_ippair(cls,graphs):
        dep = DependencyGraph()
        dep.nodes = []
        for g in graphs:
            dep.nodes.extend(g.nodes)
        return dep

    @classmethod
    def construct(cls, path1,path2,ip1,ip2):
        """
        construct block-hole-free dependency graph
        :param path1: path list before update, can be None
        :param path2: path list after update
        :return: dependency graph
        """
        priority = 5

        dep = DependencyGraph()
        if path1==None:
            dep.nodes = [DependencyNode(Operation(
                Operation.ADD,
                port[0],
                priority,
                {"eth_type":0x0800,"ipv4_src":ip1,"ipv4_dst":ip2},
                {"output":[port[1]]}
            )) for port in path2]
            first = dep.nodes[0]
            for node in dep.nodes[1:]:
                node.acquire.append(first)
                first.release.append(node)
        else:
            op_map = {}
            path1_map = dict(path1)
            path2_map = dict(path2)
            for n,p in path2_map.items():
                if n in path1_map:
                    if p!=path1_map[n]:
                        op_map[n]= DependencyNode(Operation(
                            Operation.MOD,
                            n,
                            priority,
                            {"eth_type":0x0800,"ipv4_src":ip1,"ipv4_dst":ip2},
                            {"output":[p]}
                        ))
                else:
                    op_map[n] = DependencyNode(Operation(
                        Operation.ADD,
                        n,
                        priority,
                        {"eth_type": 0x0800, "ipv4_src": ip1, "ipv4_dst": ip2},
                        {"output": [p]}
                    ))
            for n,p in path1_map.items():
                if n not in path2_map:
                    op_map[n]= DependencyNode(Operation(
                        Operation.DEL,
                        n,
                        priority,
                        {"eth_type":0x0800,"ipv4_src":ip1,"ipv4_dst":ip2},
                        {"output":[p]}
                    ))
            firstmod = None
            for n,p in path2:
                if n not in op_map:
                    continue
                node = op_map[n]
                if node.op.type == Operation.MOD:
                    if firstmod is not None:
                        node.release.append(firstmod)
                        firstmod.acquire.append(node)
                    firstmod = node
                elif node.op.type == Operation.ADD:
                    if firstmod is not None:
                        node.release.append(firstmod)
                        firstmod.acquire.append(node)
                else:
                    raise Exception("unexpected operation")
            for n,p in path1:
                if n not in op_map:
                    continue
                node = op_map[n]
                if node.op.type == Operation.MOD:
                    firstmod = node
                elif node.op.type == Operation.DEL:
                    if firstmod is not None:
                        node.acquire.append(firstmod)
                        firstmod.release.append(node)
                else:
                    raise Exception("unexpected operation")
            dep.nodes = list(op_map.values())
        dep.path1 = path1
        dep.path2 = path2
        dep.ip1 = ip1
        dep.ip2 = ip2
        return dep